

// ----------------------- s: building super html obj

function shCreate(){
    var shl = new Object;
    shl['/shobj'] = 1;
    shl['/'] = new Array;
    var content = shl['/'];

    var argv = shCreate.arguments;
    var argc = argv.length;

    for(var i = 0; i < argc; i++){
        content[content.length] = argv[i];
    }
    return shl
}

function shPush(shl){
    if(shl['/shobj'] != 1){
        return false;
    }

    var content = shl['/'];

    var flag = false;
    var argv = shPush.arguments;
    var argc = argv.length;

    for(var i = 0; i < argc; i++){
        if(flag)
            content[content.length] = argv[i];
        else
        flag = true;
        }
        return null;
        }

        function shSet(shl, templateName, subShl){
        shl[templateName] = subShl; 
        }

        function shCopy(shl, templateName, subShl){
        shl[templateName] = shClone(subShl);
        }

        function shClone(shl){
        if(!isArray(shl)){
        var newObj = new Object;
        } else{
    newObj = new Array;
}


for(var i in shl){
    if(typeof(shl[i]) != 'object'){
        newObj[i] = shl[i];
    } else {
        newObj[i] = shClone(shl[i]);
    }
}
return newObj;
}

function shHtml(shl, indentation, newline){
    indentation = setDefault(indentation, true);
    newline = setDefault(newline, true);
    ddebug(shl);

    if(shl['/shobj'] == 1){
        var compiledInstr = [];
        var r = _parse_shl_code(compiledInstr, shl['/'], shl, indentation, newline);
        if(r != null){
            alert(r);
            return '';
        }
        ddebug(compiledInstr, 'compiled instr');

        //        var compiledInstrObj = {
        //            instr: compiledInstr,
        //            ptr: 0
        //        };
        //        var finalInstr = [];
        //        r = _populate_sh_instr(shl, compiledInstrObj, finalInstr, null, true, -1);
        //        if(r != null){
        //            return '';
        //        }
        //        delete compiledInstrObj;
        //        delete compiledInstr;
        //        ddebug('final instr', finalInstr);

        var tagStackObj = {
            stack:[{
                content:[]
            }],
            ptr:0
        }
        r = _compile_tree_obj(compiledInstr, tagStackObj);
        if(r != null){
            return '';
        }
        delete compiledInstr;

        var htmlText = [];
        r = _generate_html(tagStackObj.stack[0]['content'], htmlText, 0, indentation, newline)
        if(r != null){
            return '';
        }
        delete tagStackObj;

        return htmlText.join('');
    } else {
        if (typeof(shl) == 'string'){
            return shl;
        } else {
            return '';
        }
    }

}


function shQuick(){
    var argv = shQuick.arguments;
    var argc = argv.length;
    var funcArgv = [];

    for(var i = 0; i < argc; i++){
        funcArgv[funcArgv.length] = argv[i];
    }

    return shHtml(shCreate(funcArgv));
}

// ---------------------------- shl util functions

function _object_size(obj){
    var len = 0;
    if(typeof(obj) == 'object'){
        for(var i in obj){
            if(typeof(obj) != 'function'){
                len ++;
            }
        }
    }
    return len;
}

function _is_word_char(c){
    var code = c.charCodeAt(0);
    return(code >= 65 && code <= 90) || (code >= 97 && code <= 122) ||
    (code >= 48 && code <= 57) || c == '_';
}

function _is_space_char(c){
    return c == ' ' || c == '\n' || c == '\t';
}

function _is_integer(s){
    return s == parseInt(s);
}

/**
 * @param curr
 * str | ptr
 */
function _get_word(curr){
    var r = [];

    for(var strlen = curr.str.length; curr.ptr < strlen; curr.ptr ++){
        var c = curr.str.charAt(curr.ptr);
        if(_is_word_char(c)){
            r[r.length] = c;
        } else {
            return r.join('');
        }
    }

    return r.join('');
}

function _escape_for_html(s){
    return htmlentities(s.toString());
}

function _escape_for_attr(s){
    return s.toString().split('\'').join('\\\'');
}

function _get_value_for_var(vtype, value, isAttr){
    if(typeof value == 'undefined') value = null;
    isAttr = setDefault(isAttr, true);

    if(value === null){
        return null;
    }
    if(vtype == 's'){
        if(isArray(value)){
            if(isAttr){
                value = value.join('');
            } else{
                value = value.join('\n');
            }
        }
        if(isAttr){
            return _escape_for_attr(value);
        } else {
            return _escape_for_html(value);
        }
    } else if (vtype == 'd'){
        if(isArray(value)){
            return null;
        }
        return parseInt(value).toString();
    } else if(vtype == 'f'){
        if(isArray(value)){
            return null;
        }
        return parseFloat(value);
    } else if(vtype == 'ne'){
        if(isArray(value)){
            if(isAttr){
                return value.join('');
            } else {
                return value.join('\n');
            }
        }
        return value.toString();
    } else {
        return null;
    }
}

function _get_until_not_char(curr, cond){
    var r = [];
    for(var l = curr.str.length; curr.ptr < l; curr.ptr ++){
        var c = curr.str.charAt(curr.ptr);
        if(c == cond){
            r[r.length] = c;
        } else {
            break;
        }
    }

    return r.join('');
}

function _get_until_char(curr, cond){
    var r = [];
    for(var l = curr.str.length; curr.ptr < l; curr.ptr ++){
        var c = curr.str.charAt(curr.ptr);
        if(c != cond){
            r[r.length] = c;
        } else {
            var res = new Object;
            res['cond'] = true;
            res['r'] = r.join('');
            return res;
        }
    }

    res = new Object;
    res['cond'] = false;
    res['r'] = r.join('');
    return res;
}

function _get_char_for_attr(c){
    if(c == "'"){
        return "\\'";
    } else{
        return c;
    }
}

function _if_match_close_cond(cond, tag){
    if('tag' in cond && tag['tag'] != cond['tag']){
        return false;
    }
    if('id' in cond && tag['id'] != cond['id']){
        return false;
    }
    if(typeof(cond['class']) == 'object'){
        for(var cls in cond['class']){
            if(typeof(tag['class']) != 'object'){
                return false;
            }
            if(!(cls in tag['class'])){
                return false;
            }
        }
    }
    if('hidden' in cond && tag['hidden'] != cond['hidden']){
        return false;
    }
    ddebug('cond', cond, 'tag', tag);
    return true;
}

function _skip_all_space(curr){
    var l = curr.str.length;
    while(curr.ptr < l){
        var c = curr.str.charAt(curr.ptr);
        if(!_is_space_char(c)){
            return c;
        }
        curr.ptr ++;
    }
    return false;
}



// ----------------------------- first run : parsing shl

function _parse_shl_code(compiledInstr, currAry, shObj, indentation, newline){
    var currAryPtr = 0;
    var currAryLength = currAry.length;
    while(currAryPtr < currAryLength){
        var curr = {
            str:currAry[currAryPtr],
            ptr:0
        };


        if(typeof(curr.str) != 'object'){
            var strLen = curr.str.length;
            while(curr.ptr < strLen){
                var c = curr.str.charAt(curr.ptr);
                if(_is_word_char(c)){
                    // --------------- tag, attr, for|if --------------
                    var res = _parse_char(curr);
                    if(res === false){
                        return "Error @ '" + curr.str + "' #1";
                    }
                    if(res['type'] == 'attr' || res['type'] == 'if'){
                        if(!('f' in res)){
                            if(res['name'] == '' || !('name' in res)){
                                res['content'] = _get_value_for_var(res['vtype'], currAry[++currAryPtr], true);
                            } else {
                                res['content'] = _get_value_for_var(res['vtype'], shObj[res['name']], true);
                            }
                        } else {
                            res['content'] = _get_value_for_var('ne', res['content'], true);
                        }
                    }
                    compiledInstr[compiledInstr.length] = res;
                } else if(c == ':'){
                    // ------------- html ---------------
                    curr.ptr ++;
                    res = _parse_attr(curr);
                    if(res === false){
                        return "Error @'" + curr.str + "' #2";
                    }
                    res['type'] = 'html';
                    if(!('f' in res)){
                        if(res['name'] == '' || !('name' in res)){
                            res['content'] = _get_value_for_var(res.vtype, currAry[++currAryPtr], false);
                        } else {
                            res['content'] = _get_value_for_var(res['vtype'], shObj[res['name']], false);
                        }
                    } else {
                        res['content'] = _get_value_for_var('ne', res['content'], false);
                    }
                    ddebug(res);
                    compiledInstr[compiledInstr.length] = res;
                } else if(c == '/'){
                    // ------------------ template var || //efor || //eif--------------------
                    c = curr.str.charAt(++curr.ptr);
                    if(_is_word_char(c)){
                        var w = _get_word(curr);
                        if(w != ''){
                            res = {
                                type:'html'
                            };
                            if(w in shObj){
                                res['content'] = shHtml(shObj[w]);
                            }
                            compiledInstr[compiledInstr.length] = res;
                        } else {
                            return "Error @ '" + curr.str + "' #3";
                        }
                    } else if (c == '/'){
                        curr.ptr++;
                        w = _get_word(curr);
                        if(w == 'efor' || w == 'eif'){
                            res = {
                                type: w
                            };
                            compiledInstr[compiledInstr.length] = res;
                        } else {
                            return "Error @ '" + curr.str + "' #4";
                        }
                    } else {
                        return "Error @ '" + curr.str + "' #5";
                    }
                } else if(c == '&'){
                    // ----------------- quick html ------------------
                    curr.ptr ++;
                    var quickHtml = _get_until_char(curr, '&');
                    curr.ptr ++;
                    if(quickHtml['cond']){
                        res = {
                            type:'html',
                            content: _escape_for_html(quickHtml['r'])
                        };
                        compiledInstr[compiledInstr.length] = res;
                    } else {
                        return "Error @ '" + curr.str + "' #6";
                    }
                } else if(c == '<'){
                    var r = _get_until_not_char(curr, '<');
                    res = {
                        type: 'close'
                    };
                    if(r.length == 1){
                        // only one '<'

                        if(_is_space_char(curr.str.charAt(curr.ptr)) || curr.ptr == strLen){
                            res['level'] = 1;
                        } else {
                            var tmpPtr = curr.ptr;
                            w = _get_word(curr);
                            if(_is_integer(w)){
                                // if there is a number after <
                                res['level'] = parseInt(w);
                            } else {
                                // if there is a set of closing conditions
                                // reset the ptr, because the word is not numeric, we need to do it again
                                curr.ptr = tmpPtr;
                                r = _parse_conditional_close(curr);
                                if(r != false){
                                    res['cond'] = r;
                                } else {
                                    return "Error @ '" + curr.str + "' #7";
                                }
                            } // e : else
                        } // e: else
                    } else {
                        res['level'] = r.length;
                    }
                    compiledInstr[compiledInstr.length] = res;
                } else if(_is_space_char(c)){
                    curr.ptr ++;
                } else {
                    return "Error @ '" + curr.str + "' #8";
                }
            }
        } else if(curr.str['/shobj'] == 1) {
            res = {
                type: 'html',
                content: shHtml(curr.str, indentation, newline)
            }
            compiledInstr[compiledInstr.length] = res;
        } else if (isArray(curr.str)){
            r = _parse_shl_code(compiledInstr, curr.str, shObj, indentation, newline);
            if(r != null){
                return r;
            }
        }
        currAryPtr ++;

        ddebug(currAryPtr < currAryLength, currAryPtr, currAryLength);
    }

    return null;
}

function _parse_char(curr){
    var w = _get_word(curr);
    var c = curr.str.charAt(curr.ptr);

    if(c == '#' || c == '.' || c == '?' || c == '>' || c == '/'){
        var res = _parse_tag(curr);
        ddebug(curr.str, 'curr.str');
        if(res === false)
            return false;
        res['tag'] = w;
        res['type'] = 'tag';
    } else if(c == ':'){
        curr.ptr ++;
        res = _parse_attr(curr);
        res['attr'] = w;
        res['type'] = 'attr';
    } else if (c == '('){
        if(w == 'for' || w == 'if'){
            curr.ptr ++;
            res = _parse_var(curr);
            ddebug(res, 'for if');
            if(res === false || (w == 'for' && res['vtype'] != 'ne')){
                // the return shouldn't be false, and there must be no vtype rather than 'ne' for 'for'
                return false;
            }
            res['type'] = w;

            if(w == 'for'){
                c = _skip_all_space(curr);
                if(c != ')'){
                    // there must be no other extra chars than spaces
                    // and a ')' must be met before the string ends
                    return false;
                }
            } else {
                if(!_is_space_char(curr.str.charAt(curr.ptr))){
                    return false;
                }
                curr.ptr ++;
                var value = _get_until_char(curr, ')');
                if(value['cond']){
                    res['value'] = $.trim(value['r']);
                } else {
                    return false;
                }
            }
            curr.ptr ++;
        } else {
            return false;
        }
    } else {
        return false;
    }

    return res;
}

function _parse_var(curr){
    var strLen = curr.str.length;
    while (curr.ptr < strLen){
        var c = curr.str.charAt(curr.ptr);

        if(c == '~'){
            curr.ptr++;
            var w = _get_word(curr);
            if(w == 's' || w == 'd' || w == 'ne' || w == 'f'){
                var res = {
                    vtype: w,
                    name: ''
                }

                if(curr.str.charAt(curr.ptr) == '/'){
                    curr.ptr++;
                    w = _get_word(curr);
                    if(w != ''){
                        res['name'] = w;
                    } else {
                        return false;
                    }
                }
                return res;
            }
        } else if(c == '/'){
            curr.ptr ++;
            w = _get_word(curr);
            if(w != ''){
                res = {
                    vtype: 'ne',
                    name: w
                }
                return res;
            }
            else {
                return false;
            }
        } else if (!_is_space_char(c)){
            return false;
        }

        curr.ptr ++;
    }

    return false;
}

function _parse_attr(curr, isAttr){
    isAttr = setDefault(isAttr, true);

    var content = new Array;
    var isWaitingForVarDef = true;
    var strLen = curr.str.length;
    while(curr.ptr < strLen){
        var c = curr.str.charAt(curr.ptr);
        if(isWaitingForVarDef && c == '~'){
            curr.ptr ++;
            var w = _get_word(curr);
            if(w == 's' || w == 'd' || w == 'ne' || w == 'f'){
                var res = {
                    vtype: w
                };
                if(curr.str.charAt(curr.ptr) == '/'){
                    curr.ptr++;
                    w = _get_word(curr);
                    if(w != ''){
                        res['name'] = w;
                    } else {
                        return false;
                    }
                } else {
                    res['name'] = '';
                }

                ddebug('attr raw', res);
                return res;
            } else {
                content[content.length] = '~';
                if(w != ''){
                    content[content.length] = w;
                }
                isWaitingForVarDef = false;
                continue;
            }
        } else if (isWaitingForVarDef && c == '/'){
            curr.ptr++;
            w = _get_word(curr);
            if(w != ''){
                res = {
                    vtype: 'ne',
                    name: w
                };
                return res;
            } else {
                isWaitingForVarDef = false;
                //                curr.ptr--
                continue;
            }
        } else if(c == ','){
            if(curr.str.charAt(curr.ptr + 1) == ','){
                curr.ptr += 2;

                return {
                    content: content.join(''),
                    f: 1
                };
            } else {
                content[content.length] = c;
            }
        } else if(c == '/'){
            curr.ptr ++;
            if(curr.str.charAt(curr.ptr) == ','){
                content[content.length] = ',';
            } else {
                content[content.length] = '/';
                content[content.length] = curr.str.charAt(curr.ptr);
            }
        } else if (isWaitingForVarDef){
            if(!_is_space_char(c)){
                isWaitingForVarDef = false;
                content[content.length] = c;
            }
        } else {
            content[content.length] = c;
        }
        curr.ptr ++;
    }

    res = {
        vtype: 'ne'
    };
    return res;
}

function _parse_tag(curr){
    var res = {
        content:[]
    };

    var strLen = curr.str.length;
    while(curr.ptr < strLen){
        var c = curr.str.charAt(curr.ptr);
        if(c == '#'){ // id
            curr.ptr++;
            var w = _get_word(curr);
            if(w == ''){
                return false;
            }
            res['id'] = w;
        } else if(c == '.'){ // class
            curr.ptr++;
            w = _get_word(curr);
            if(w == ''){
                return false;
            }
            res['class'] = res['class'] || {};
            res['class'][w] = 1;
        } else if (c == '?'){
            curr.ptr++;
            w = _get_word(curr);
            if(w == ''){
                return false;
            }
            res['hidden'] = w;
        } else if (c == '>'){
            curr.ptr ++;
            var condClose = _parse_conditional_close(curr);
            ddebug(condClose, condClose === false, 'what cond');
            if(condClose === false){
                ddebug(curr, 'why');
                return false;
            } else if(condClose.length > 0){
                res['cond'] = condClose;
            }
            res['level'] = 'n';
            return res;
        } else if (c == '/'){
            curr.ptr ++;
            res['level'] = 1;
            return res;
        } else{
            // syntax error, return false
            return false;
        }
    }

    return false;
}

function _parse_conditional_close(curr){
    var conds = [];
    var w = _get_word(curr);
    var res = {};

    if(w != ''){
        res['tag'] = w;
    }

    var strLen = curr.str.length;
    while(curr.ptr < strLen){
        var c = curr.str.charAt(curr.ptr);
        ddebug(c, 'c');
        if(c == '#'){ // id
            curr.ptr ++;
            w = _get_word(curr);

            if(w == ''){
                return false;
            }
            res['id'] = w;
        } else if(c == '.'){ // class
            curr.ptr ++;
            w = _get_word(curr);

            if(w == ''){
                return false;
            }
            res['class'] = res['class'] || {};
            res['class'][w] = 1;
            ddebug(res);
        } else if(c == '?'){
            curr.ptr ++;
            w = _get_word(curr);

            if(w == ''){
                return false;
            }
            res['hidden'] = w;
        } else if(c == '|'){
            ddebug(_object_size(res), '|');
            curr.ptr ++;
            if(_object_size(res) > 0){
                conds[conds.length] = res;
                res = {};
                w = _get_word(curr);
                if(w != ''){
                    res['tag'] = w;
                }
            } else {
                return false;
            }
        } else if(_is_space_char(c)){
            ddebug(res, 'res');
            if(_object_size(res) > 0){
                conds[conds.length] = res;
            }
            ddebug('right before');
            return conds;
            ddebug('right after');
        } else {
            return false;
        }
    }

    // end of the line, return the result
    if(_object_size(res) > 0){
        conds[conds.length] = res;
    }
    return conds;
}

// --------------------------- second run : compiled super html obj

//function _populate_sh_instr(data, compiledInstrObj, finalInstr, target, ifCondMet, count){
//    target = setDefault(target, null);
//    ifCondMet = setDefault(ifCondMet, true);
//    count = setDefault(count, -1);
//    if(count == -1){
//        count = compiledInstrObj.instr.length;
//    }
//    while(compiledInstrObj.ptr < count){
//        var instr = compiledInstrObj.instr[compiledInstrObj.ptr];
//        compiledInstrObj.ptr ++;
//
//        if(instr['type'] == target){
//            return null;
//        }
//
//        if(instr['type'] == 'for'){
//            var goneThrough = false;
//            var tmpPtr = compiledInstrObj.ptr;
//            if(isArray(data[instr['name']])){
//                var dataContent = data[instr['name']];
//                for(var i in dataContent){
//                    var d = dataContent[i];
//                    if(typeof(d) == 'object'){
//                        ddebug(dataContent);
//                        goneThrough = true;
//                        compiledInstrObj.ptr = tmpPtr;
//                        var r = _populate_sh_instr(d, compiledInstrObj, finalInstr, 'efor', ifCondMet, count);
//                        if(r != null){
//                            return r;
//                        }
//                    }
//                }
//                continue;
//            }
//            if(!goneThrough){
//                r = _populate_sh_instr({}, compiledInstrObj, finalInstr, 'efor', false, count);
//                if(r != null){
//                    return r;
//                }
//            }
//        }
//
//        // first populate var into the variable
//        if('vtype' in instr){
//            instr['content'] = _get_value_for_var(instr['vtype'], data[instr['name']], instr['type'] == 'attr'? true: false);
//        }
//        if(instr['type'] == 'if'){
//            ddebug(instr, 'if');
//            if(instr['content'] == instr['value']){
//                r = _populate_sh_instr(data, compiledInstrObj, finalInstr, 'eif', ifCondMet, count);
//                if (r != null){
//                    ddebug(r);
//                    return r;
//                }
//            } else {
//                r = _populate_sh_instr(data, compiledInstrObj, finalInstr, 'eif', false, count);
//                if (r != null){
//                    ddebug(r);
//                    return r;
//                }
//            }
//            continue;
//        }
//
//        if(ifCondMet){
//            finalInstr[finalInstr.length] = shClone(instr);
//        }
//    }
//
//    if(target != null){
//        return "Error @ no " + target + " met";
//    } else {
//        return null;
//    }
//}

// ------------------------------------- third run : compiling tree object

function _compile_tree_obj(finalInstr, tagStackObj){
    var lastTagLevel = {
        val: 'n'
    }
    for(var k in finalInstr){
        var instr = finalInstr[k];
        switch(instr['type']){
            case 'tag':
                _push_tag(tagStackObj, instr, lastTagLevel);
                break
            case 'html':
                _push_html(tagStackObj, instr);
                break;
            case 'attr':
                _push_attr(tagStackObj, instr);
                break;
            case 'close':
                _close_tag(tagStackObj, instr, lastTagLevel);
                break;
            case 'comment':
                _push_commet(tagStackObj, instr);
                break;
        }
    }
}

function _push_html(tagStackObj, instr){
    if(instr['content'] != null && instr['content'] != ''){
        var s = tagStackObj.stack[tagStackObj.ptr]['content'];
        s[s.length] = instr['content'];
    }
}

function _push_attr(tagStackObj, instr){
    var currObj = tagStackObj.stack[tagStackObj.ptr];
    if(instr['content'] != null){
        if(instr['attr'] == 'id'){
            currObj['id'] = instr['content'];
        } else if(instr['attr'] == 'class'){
            currObj['class'] = {};
            var classes = instr['content'].split(' ');
            for(var i in classes){
                if(classes[i] != ''){
                    currObj['class'][classes[i]] = 1;
                }
            }
        } else {
            currObj['attr'] = currObj['attr'] || {};
            var attr = currObj['attr'];
            attr[instr['attr']] = instr['content'];
        }
    }
}

function _push_comment(tagStackObj, instr){
    if(instr['content'] != null && instr['content'] != ''){
        var content = tagStackObj.stack[tagStackObj.ptr]['content'];
        content[content.length] = ["<!--", instr['content'], "-->"].join('');
    }
}

function _close_tag(tagStackObj, instr, lastTagLevel){
    if(instr['level'] > 0){
        tagStackObj.ptr -= instr['level'];
        if(tagStackObj.ptr < 0){
            tagStackObj.ptr = 0;
        }
        lastTagLevel.val = 'n';
    } else {
        for (var i = tagStackObj.ptr; i >= 0; i--){
            for(var k in instr['cond']){
                if(typeof(instr['cond'][k]) == 'object' && _if_match_close_cond(instr['cond'][k], tagStackObj.stack[i])){
                    tagStackObj.ptr = i - 1 < 0 ? 0 : i - 1;
                    lastTagLevel.val = 'n';
                    return;
                }
            }
        }
    }
}

function _push_tag(tagStackObj, tag, lastTagLevel){
    _pop_tag(tagStackObj, tag, lastTagLevel);

    var newTag = tag;
    var content = tagStackObj.stack[tagStackObj.ptr]['content'];
    content[content.length] = newTag;
    tagStackObj.ptr ++;
    tagStackObj.stack[tagStackObj.ptr] = newTag;
    lastTagLevel.val = tag['level'];
}

function _pop_tag(tagStackObj, tag, lastTagLevel){

    for(var i = 0; i <= tagStackObj.ptr; i++){
        if('cond' in tagStackObj.stack[i]){
            var conds = tagStackObj.stack[i]['cond'];
            ddebug('conds', conds['0']);
            for(var k in conds){
                ddebug(conds[k], 'tag', tag);
                if(typeof(conds[k]) ==  'object' && _if_match_close_cond(conds[k], tag)){
                    ddebug(conds[k], tag);
                    tagStackObj.ptr = i - 1< 0? 0 : i-1;
                    return;
                }
            }
        }
    }

    if(lastTagLevel.val == '1'){
        if(tagStackObj.ptr > 0){
            tagStackObj.ptr --;
        }
    }
}

// ---------------------------- forth run : generating html text

function _generate_html(treeNodes, htmlText, level, indentation, newline){
    indentation = setDefault(indentation, true);
    newline = setDefault(newline, true);

    if(isArray(treeNodes)){
        for(var k in treeNodes){
            var n = treeNodes[k];
            if(n['type'] == 'tag'){
                if(indentation)
                    _print_indentation(level, htmlText);
                htmlText[htmlText.length] = '<';
                htmlText[htmlText.length] = n['tag'];
                if(n['id'] != null){
                    htmlText[htmlText.length] = ' id=\'';
                    htmlText[htmlText.length] = n['id'];
                    htmlText[htmlText.length] = '\'';
                }
                if(typeof(n['class']) == 'object'){
                    htmlText[htmlText.length] = ' class=\'';
                    htmlText[htmlText.length] = Object.keys(n['class']).join(' ');
                    htmlText[htmlText.length] = '\'';
                }
                if(typeof(n['attr']) == 'object'){
                    for(var attr in n['attr']){
                        htmlText[htmlText.length] = ' ';
                        htmlText[htmlText.length] = attr;
                        htmlText[htmlText.length] = '=\'';
                        htmlText[htmlText.length] = n['attr'][attr];
                        htmlText[htmlText.length] = '\'';
                    }
                    if((n['tag'] == 'input' || n['tag'] == 'select') && n['id'] != null && !('name' in n['attr'])){
                        // pop: if the elem is 'input', set name = id by default. 16/2
                        htmlText[htmlText.length] = ' name=\'';
                        htmlText[htmlText.length] = n['id'];
                        htmlText[htmlText.length] = '\'';
                    }
                } else if((n['tag'] == 'input' || n['tag'] == 'select') && n['id'] != null){
                    htmlText[htmlText.length] = ' name=\'';
                    htmlText[htmlText.length] = n['id'];
                    htmlText[htmlText.length] = '\'';
                }
                
                if(n['tag'] == 'input'){
                    htmlText[htmlText.length] = newline? '/>\n' : '/>';
                    continue;
                }
                htmlText[htmlText.length] = newline? '>\n' : '>';

                if(n['tag'] == 'link' || n['tag'] == 'meta'){
                    continue;
                }

                if(isArray(n['content']) && n['content'].length > 0){
                    _generate_html(n['content'], htmlText, level + 1, indentation, newline);
                }
                if(indentation)
                    _print_indentation(level, htmlText);
                htmlText[htmlText.length] = '</';
                htmlText[htmlText.length] = n['tag'];
                htmlText[htmlText.length] = newline? '>\n' : '>';
            } else {
                if(typeof(n) == 'string' || typeof(n) == 'number'){
                    if(indentation){
                        _print_indentation(level, htmlText);
                    }
                    htmlText[htmlText.length] = n;
                    if(newline){
                        htmlText[htmlText.length] = '\n';
                    }
                }
            }
        }
    }
}

function _print_indentation(level, htmlText){
    for (i = 0; i < level; i++){
        htmlText[htmlText.length] = '   ';
    }
}

Object.keys = Object.keys || function(o) {
    var result = [];
    for(var name in o) {
        if (o.hasOwnProperty(name))
            result.push(name);
    }
    return result;
}